Louisville Weather Time Series Analysis
This is the seconds in a series of files regarding the analysis of Weather Data in Louisville.
For this analysis I will be using a few packages which you will need loaded in order to follow along:
# List of packages to load:
packages <- c("tidyverse", "lubridate", "tibbletime", "rlang", "dygraphs", "forecast", "zoo", "xts", "stringr")
# Check to see whether any packages aren't installed on the computer and install
new_packages <- packages[!(packages %in% installed.packages()[,"Package"])]
if(length(new_packages)) install.packages(new_packages)
# Load Neccessary Packages
sapply(packages, require, character.only = TRUE)
rm(new_packages)
Aggregate data to daily
Next step is to aggregate all of the data into daily values.
Start with a function to make it clearer and easier.
tt_period_apply <- function(in.tbl_time, in.period, in.func = mean, na_rm = TRUE) {
# Programatically find the index column
index_column <- attributes(in.tbl_time)$index_quo[[2]]
# Collapse by period, group by index, then summarise by function.
out.tbl_time <- in.tbl_time %>%
collapse_by(in.period) %>%
group_by(!!! sym(index_column)) %>%
summarise_if(is.numeric, in.func, na.rm = na_rm)
}
Aggregate Temperature
temporary.tt <- bf.tt %>% select(DATE_TIME,TEMP)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = mean, na_rm = TRUE) %>%
select(DATE_TIME, mean.temp = TEMP)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = max, na_rm = TRUE) %>%
select(DATE_TIME, max.temp = TEMP) %>%
full_join(bf.daily.tt,by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = min, na_rm = TRUE) %>%
select(DATE_TIME, min.temp = TEMP) %>%
full_join(bf.daily.tt,by = 'DATE_TIME')
rm(temporary.tt)
Convert Wind Direction to N/S and E/W values
temporary.tt <- bf.tt %>% select(DATE_TIME, DIR)
# Convert directions in degrees to Radians, then to x and y
temporary.tt <- temporary.tt %>%
mutate(dir.x = round(sin(DIR*pi/180),digits = 6),
dir.y = round(cos(DIR*pi/180),digits = 6))
Aggregate Wind Direction X and Y
temporary.tt <- temporary.tt %>% select(DATE_TIME,dir.x, dir.y)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = mean, na_rm = TRUE) %>%
select(DATE_TIME, mean.dir.x = dir.x, mean.dir.y = dir.y) %>%
full_join(bf.daily.tt, by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = min, na_rm = TRUE) %>%
select(DATE_TIME, min.dir.x = dir.x, min.dir.y = dir.y) %>%
full_join(bf.daily.tt, by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = max, na_rm = TRUE) %>%
select(DATE_TIME, max.dir.x = dir.x, max.dir.y = dir.y) %>%
full_join(bf.daily.tt, by = 'DATE_TIME')
rm(temporary.tt)
Aggregate Wind Speed
temporary.tt <- bf.tt %>% select(DATE_TIME,SPD)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = mean, na_rm = TRUE) %>%
select(DATE_TIME, mean.spd = SPD) %>%
full_join(bf.daily.tt,by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = max, na_rm = TRUE) %>%
select(DATE_TIME, max.spd = SPD) %>%
full_join(bf.daily.tt,by = 'DATE_TIME')
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = min, na_rm = TRUE) %>%
select(DATE_TIME, min.spd = SPD) %>%
full_join(bf.daily.tt,by = 'DATE_TIME')
rm(temporary.tt)
Aggregate Precipitation
temporary.tt <- bf.tt %>% select(DATE_TIME, PCP01)
bf.daily.tt <- tt_period_apply(temporary.tt, 'daily', in.func = sum, na_rm = TRUE) %>%
select(DATE_TIME, sum.precip = PCP01) %>%
full_join(bf.daily.tt,by = 'DATE_TIME')
rm(temporary.tt)
Add new column for days since last Precipitation
bf.daily.tt <- bf.daily.tt %>%
mutate(days.since.precip = NA)
rain_data_detected <- FALSE
for(row in 1:length(bf.daily.tt$days.since.precip)) {
if(rain_data_detected) {
bf.daily.tt$days.since.precip[row] <- ifelse(bf.daily.tt$sum.precip[row] <= 0.01,
ifelse(is.na(bf.daily.tt$days.since.precip[row-1]),
1,
bf.daily.tt$days.since.precip[row-1] + 1),
0)
} else {
if(bf.daily.tt$sum.precip[row] >= 0.01) {
rain_data_detected <- TRUE
bf.daily.tt$days.since.precip[row] <- 0
}
}
}
rm(rain_data_detected,row)
Determine what to do about NA values.
For most of the time series analysis the program needs to know how to handle NA values. Not an easy thing to do in some cases. Omitting NA values creates a problem because the functions assume that a seasonal year is 365 days (integer closest to 365.25), but with NA values missing that will be wrong.
tt_na_stats <- function(in.tbl_time) {
out.stats <- list()
# out.stats[["na_percent"]] <- list()
for(col in names(in.tbl_time)) {
out.stats[["na_percent"]][col] <- 100*sum(is.na(in.tbl_time[,col][[1]]))/length(in.tbl_time[,col][[1]])
}
return(out.stats)
}
tt_na_stats(bf.daily.tt)
$na_percent
DATE_TIME sum.precip min.spd max.spd mean.spd max.dir.x max.dir.y min.dir.x min.dir.y mean.dir.x
0.00000000 0.00000000 0.00000000 0.00000000 0.03650301 0.00000000 0.00000000 0.00000000 0.00000000 0.08517369
mean.dir.y min.temp max.temp mean.temp days.since.precip
0.08517369 0.00000000 0.00000000 0.10950903 61.51974205
There are only a couple of columns that have NA or NaN values.
- mean.spd
- mean.dir.x
- mean.dir.y
- mean.temp
NA values in mean.temp
It is pretty easy to handle NA values in TEMP. We can fill the positions with exptrapolated values because this will usually be close enough.
bf.daily.tt$mean.temp <- trunc(zoo::na.approx(bf.daily.tt$mean.temp))
tt_na_stats(bf.daily.tt)
$na_percent
DATE_TIME sum.precip min.spd max.spd mean.spd max.dir.x max.dir.y min.dir.x min.dir.y mean.dir.x
0.00000000 0.00000000 0.00000000 0.00000000 0.03650301 0.00000000 0.00000000 0.00000000 0.00000000 0.08517369
mean.dir.y min.temp max.temp mean.temp days.since.precip
0.08517369 0.00000000 0.00000000 0.00000000 61.51974205
NA values in mean.dir
NA values in mean.dir likely indicates there wasn’t enough data in that field to get an average. Therefore, the best course of action is to replace both values with 0 which will indicate the direction wasn’t moving in either the x direction or the y direction. Note that this could cause issues when trying to turn x and y values back into compass directions.
bf.daily.tt$mean.dir.x <- ifelse(is.na(bf.daily.tt$mean.dir.x),0,bf.daily.tt$mean.dir.x)
bf.daily.tt$mean.dir.y <- ifelse(is.na(bf.daily.tt$mean.dir.y),0,bf.daily.tt$mean.dir.y)
tt_na_stats(bf.daily.tt)
$na_percent
DATE_TIME sum.precip min.spd max.spd mean.spd max.dir.x max.dir.y min.dir.x min.dir.y mean.dir.x
0.00000000 0.00000000 0.00000000 0.00000000 0.03650301 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
mean.dir.y min.temp max.temp mean.temp days.since.precip
0.00000000 0.00000000 0.00000000 0.00000000 61.51974205
NA values in mean.spd
NA values in speed are a bit harder to decide what to do with. There is no way of knowing whether the issue was that the machine measuring the data was down, or whether there just wasn’t any speed. Also, wind is extremely variable from day to day. Therefore it is decided to replace values with 0 for the beginning analysis, even though this may not be the best idea.
bf.daily.tt$mean.spd <- ifelse(is.na(bf.daily.tt$mean.spd),0,bf.daily.tt$mean.spd)
tt_na_stats(bf.daily.tt)
$na_percent
DATE_TIME sum.precip min.spd max.spd mean.spd max.dir.x max.dir.y min.dir.x min.dir.y mean.dir.x
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000
mean.dir.y min.temp max.temp mean.temp days.since.precip
0.00000 0.00000 0.00000 0.00000 61.51974
Inf values in any column
Some of the values are Inf because of errors in handling missing data. There are only a few instances of this, therefore they will all be converted to
for(col in names(select(bf.daily.tt,-DATE_TIME))) {
bf.daily.tt[col] <- (1-is.infinite(bf.daily.tt[,col][[1]]))*bf.daily.tt[col]
}
rm(col)
Plot Updated Daily Data
Temperature
temp.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),max.temp,mean.temp,min.temp),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
temp.daily.xts %>% dygraph(main="Daily Average Bowman Field Temperature (F)") %>%
dyAxis('y', label = "Temperature (F)") %>%
dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
dyUnzoom()
Wind Direction
dir.x.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),mean.dir.x),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
dir.x.daily.xts %>% dygraph(main="Average Daily Bowman Field Wind Direction (-1 < x < 1)") %>%
dyAxis('y', label = "Daily Wind Direction (Compass Degrees)") %>%
dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
dyUnzoom()
dir.y.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),mean.dir.y),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
dir.y.daily.xts %>% dygraph(main="Average Daily Bowman Field Wind Direction (-1 < y < 1)") %>%
dyAxis('y', label = "Daily Wind Direction (Compass Degrees)") %>%
dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
dyUnzoom()
Wind Speed
spd.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),mean.spd),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
spd.daily.xts %>% dygraph(main="Average Daily Bowman Field Wind Speed (mph)") %>%
dyAxis('y', label = "Wind speed (mph)") %>%
dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
dyUnzoom()
Precipitation
precip.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),sum.precip),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
precip.daily.xts %>% dygraph(main="Daily Bowman Field Precipitation (Inches)") %>%
dyAxis('y', label = "Precipitation (In)") %>%
dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
dyUnzoom()
Days Since Last Rain
days.since.precip.daily.xts <- xts(select(filter_time(bf.daily.tt, '2012' ~ '2017'),days.since.precip),order.by = filter_time(bf.daily.tt, '2012' ~ '2017')$DATE_TIME)
days.since.precip.daily.xts %>% dygraph(main="Bowman Field Days Since Rain") %>%
dyAxis('y', label = "Days Since Rain") %>%
dyRangeSelector(dateWindow = c("2016-01-01","2017-12-31")) %>%
dyUnzoom()
LS0tDQp0aXRsZTogIkJvd21hbiBGaWVsZCBXZWF0aGVyXzAxX1Zpc3VhbGl6aW5nIGFuZCBSZS1lbmNvZGluZyBEYXRhIg0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogeWVzDQotLS0NCg0KIyMgTG91aXN2aWxsZSBXZWF0aGVyIFRpbWUgU2VyaWVzIEFuYWx5c2lzDQoNClRoaXMgaXMgdGhlIHNlY29uZHMgaW4gYSBzZXJpZXMgb2YgZmlsZXMgcmVnYXJkaW5nIHRoZSBhbmFseXNpcyBvZiBXZWF0aGVyIERhdGEgaW4gTG91aXN2aWxsZS4NCg0KDQpGb3IgdGhpcyBhbmFseXNpcyBJIHdpbGwgYmUgdXNpbmcgYSBmZXcgcGFja2FnZXMgd2hpY2ggeW91IHdpbGwgbmVlZCBsb2FkZWQgaW4gb3JkZXIgdG8gZm9sbG93IGFsb25nOg0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCiMgTGlzdCBvZiBwYWNrYWdlcyB0byBsb2FkOg0KcGFja2FnZXMgPC0gYygidGlkeXZlcnNlIiwgImx1YnJpZGF0ZSIsICJ0aWJibGV0aW1lIiwgInJsYW5nIiwgImR5Z3JhcGhzIiwgImZvcmVjYXN0IiwgInpvbyIsICJ4dHMiLCAic3RyaW5nciIpDQogIA0KIyBDaGVjayB0byBzZWUgd2hldGhlciBhbnkgcGFja2FnZXMgYXJlbid0IGluc3RhbGxlZCBvbiB0aGUgY29tcHV0ZXIgYW5kIGluc3RhbGwNCm5ld19wYWNrYWdlcyA8LSBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl0pXQ0KaWYobGVuZ3RoKG5ld19wYWNrYWdlcykpIGluc3RhbGwucGFja2FnZXMobmV3X3BhY2thZ2VzKQ0KICANCiMgTG9hZCBOZWNjZXNzYXJ5IFBhY2thZ2VzDQpzYXBwbHkocGFja2FnZXMsIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCnJtKG5ld19wYWNrYWdlcykNCmBgYA0KDQoNCiMjIERhdGEgSW1wb3J0DQoNCkRhdGEgd2FzIGltcG9ydGVkLCBjbGVhbmVkLCBhbmQgcmVndWxhcml6ZWQgaW4gV2VhdGhlcl8wMS4gSW4gb3JkZXIgdG8gaW1wb3J0IHRoZSBkYXRhIG5vdyB3ZSBqdXN0IG5lZWQgdG8gbG9hZCB0aGUgUkRhdGEgZmlsZS4NCg0KYGBge3J9DQpsb2FkKGZpbGUgPSAiQm93bWFuRmllbGRfV2VhdGhlci5SRGF0YSIpDQpgYGANCg0KDQojIyBWaXN1YWxpemUgRGF0YQ0KDQojIyMgUGxvdCBEYXRhIGFuZCBkaXNjdXNzDQoNCiMjIyMgVGVtcGVyYXR1cmUNCg0KYGBge3J9DQp0ZW1wLnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLnR0LCAnMjAxMicgfiAnMjAxNycpLFRFTVApLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQp0ZW1wLnh0cyAlPiUgZHlncmFwaChtYWluPSJCb3dtYW4gRmllbGQgVGVtcGVyYXR1cmUgKEYpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIlRlbXBlcmF0dXJlIChGKSIpICU+JQ0KICBkeVNlcmllcygiVEVNUCIsIGF4aXMgPSAneScpICU+JQ0KICBkeVJhbmdlU2VsZWN0b3IoZGF0ZVdpbmRvdyA9IGMoIjIwMTYtMDEtMDEiLCIyMDE3LTEyLTMxIikpICU+JQ0KICBkeVVuem9vbSgpDQpgYGANCg0KQSByZXZpZXcgb2YgdGhlIGdyYXBoIHNob3dzIGEgY291cGxlIG9mIHRoaW5ncyBvZiBpbnRlcmVzdC4gVGhlIGZpcnN0IGludGVyZXN0aW5nIHRoaW5nIGlzIHRoYXQgdGhlcmUgaXMgYSB3aWRlIHZhcmlhdGlvbiBpbiBkYWlseSB0ZW1wZXJhdHVyZXMsIGJ1dCBpdCBhcHBlYXJzIHRoYXQgdGhlcmUgbWF5IGJlIG1vcmUgdmFyaWF0aW9uIGJldHdlZW4gZGF5cyBkdXJpbmcgdGhlIHdpbnRlciB0aGFuIGR1cmluZyB0aGUgc3VtbWVyLiBJdCBtaWdodCBiZSBiZXR0ZXIgdG8gdXNlIGRhaWx5IE1heC9NaW4vTWVhbiBkYXRhIGZvciB0aGlzIGFuYWx5c2lzIHRoYW4gdG8gdXNlIGhvdXJseSBkYXRhLg0KDQpBY3Rpb25zIHRvIGJlIHRha2VuOg0KDQoxLiBBZ2dyZWdhdGUgdG8gZGFpbHkNCiAgaSkgQ29sdW1uIGZvciBlYWNoIG9mIE1heCwgTWluLCBNZWFuDQoNCiMjIyMgV2luZCBEaXJlY3Rpb24NCg0KYGBge3J9DQpkaXIueHRzIDwtIHh0cyhzZWxlY3QoZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JyksRElSKSxvcmRlci5ieSA9IGZpbHRlcl90aW1lKGJmLnR0LCAnMjAxMicgfiAnMjAxNycpJERBVEVfVElNRSkNCg0KZGlyLnh0cyAlPiUgZHlncmFwaChtYWluPSJCb3dtYW4gRmllbGQgV2luZCBEaXJlY3Rpb24gKENvbXBhc3MgRGVncmVlcykiKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiV2luZCBEaXJlY3Rpb24gKENvbXBhc3MgRGVncmVlcykiKSAlPiUNCiAgZHlTZXJpZXMoIkRJUiIsIGF4aXMgPSAneScpICU+JQ0KICBkeVJhbmdlU2VsZWN0b3IoZGF0ZVdpbmRvdyA9IGMoIjIwMTYtMDEtMDEiLCIyMDE3LTEyLTMxIikpICU+JQ0KICBkeVVuem9vbSgpDQpgYGANCg0KRnJvbSB0aGUNCg0KIyMjIyBXaW5kIFNwZWVkDQoNCmBgYHtyfQ0Kc3BkLnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLnR0LCAnMjAxMicgfiAnMjAxNycpLFNQRCksb3JkZXIuYnkgPSBmaWx0ZXJfdGltZShiZi50dCwgJzIwMTInIH4gJzIwMTcnKSREQVRFX1RJTUUpDQoNCnNwZC54dHMgJT4lIGR5Z3JhcGgobWFpbj0iQm93bWFuIEZpZWxkIFdpbmQgU3BlZWQgKG1waCkiKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiV2luZCBzcGVlZCAobXBoKSIpICU+JQ0KICBkeVNlcmllcygiU1BEIiwgYXhpcyA9ICd5JykgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCmBgYA0KDQpXaW5kIHNwZWVkIGFwcGVhcnMgdG8gYmUgc2Vhc29uYWwsIHdpdGggbWF4aW11bSB3aW5kIHNwZWVkIGV4cGVjdGVkIGluIHRoZSBTcHJpbmcuIA0KDQpTb21lIGFjdGlvbnMgdG8gdGFrZToNCg0KMSkgQ29udmVydCBkYXRhIHRvIGRhaWx5DQogIGkpIENvbHVtbiBmb3IgTWF4LCBNaW4sIGFuZCBNZWFuIHdpbmQgc3BlZWQgcGVyIGRheQ0KDQojIyMjIFByZWNpcGl0YXRpb24NCg0KYGBge3J9DQpwcmVjaXAueHRzIDwtIHh0cyhzZWxlY3QoZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JyksUENQMDEpLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQpwcmVjaXAueHRzICU+JSBkeWdyYXBoKG1haW49IkJvd21hbiBGaWVsZCBQcmVjaXBpdGF0aW9uIChJbmNoZXMpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIlByZWNpcGl0YXRpb24gKEluKSIpICU+JQ0KICBkeVNlcmllcygiUENQMDEiLCBheGlzID0gJ3knKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNClRoZXJlIGFyZSBhIGNvdXBsZSBvZiBpbnRlcmVzdGluZyB0aGluZ3MgdG8gdGhpbmsgYWJvdXQgb24gdGhpcyBncmFwaC4gRmlyc3QsIHRoZXJlIGFwcGVhcnMgdG8gYmUgc29tZSBzb3J0IG9mIHNlYXNvbmFsaXR5IGluIHRoZSBhbXBsaXR1ZGUgb2YgdGhlIHByZWNpcGlhdGlvbiBhbW91bnRzLiBNb3JlIHJhaW4gcGVyIGhvdXIgaXMgZXhwZWN0ZWQgZHVyaW5nIHRoZSBzdW1tZXIgd2hlbiBpdCByYWlucy4gU2Vjb25kLCB0aGlzIGdyYXBoIGlzIG9ubHkgc2hvd2luZyBhbXBsaXR1ZGUgb2YgdGhlIGRhdGEsIGJ1dCBpcyBub3QgY2xlYXIgaG93IG9mdGVuIGl0IHJhaW5zLiBUaW1lIHNpbmNlIGxhc3QgcmFpbiBtYXkgYmUgYXMgaW1wb3J0YW50IHRvIGFpciBxdWFsaXR5IGFzIHRoZSBhbW91bnQgb2YgcmFpbiBpcy4gDQoNCkFjdGlvbnMgdG8gdGFrZToNCg0KMSkgQWdncmVnYXRlIGRhdGEgdG8gZGFpbHkgYnkgdGFraW5nIFN1bSBvZiByYWluIGluIHRoYXQgZGF5LiANCjIpIENyZWF0ZSBhIG5ldyBjb2x1bW4gZm9yIGRheXMgc2luY2UgbGFzdCByYWluIChhbHNvIGRhaWx5KS4gDQoNCiMjIEFnZ3JlZ2F0ZSBkYXRhIHRvIGRhaWx5DQoNCk5leHQgc3RlcCBpcyB0byBhZ2dyZWdhdGUgYWxsIG9mIHRoZSBkYXRhIGludG8gZGFpbHkgdmFsdWVzLg0KDQpTdGFydCB3aXRoIGEgZnVuY3Rpb24gdG8gbWFrZSBpdCBjbGVhcmVyIGFuZCBlYXNpZXIuDQoNCmBgYHtyIGVjaG89VFJVRSwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFfQ0KdHRfcGVyaW9kX2FwcGx5IDwtIGZ1bmN0aW9uKGluLnRibF90aW1lLCBpbi5wZXJpb2QsIGluLmZ1bmMgPSBtZWFuLCBuYV9ybSA9IFRSVUUpIHsNCiAgIyBQcm9ncmFtYXRpY2FsbHkgZmluZCB0aGUgaW5kZXggY29sdW1uDQogIGluZGV4X2NvbHVtbiA8LSBhdHRyaWJ1dGVzKGluLnRibF90aW1lKSRpbmRleF9xdW9bWzJdXQ0KICAjIENvbGxhcHNlIGJ5IHBlcmlvZCwgZ3JvdXAgYnkgaW5kZXgsIHRoZW4gc3VtbWFyaXNlIGJ5IGZ1bmN0aW9uLg0KICBvdXQudGJsX3RpbWUgPC0gaW4udGJsX3RpbWUgJT4lDQogICAgY29sbGFwc2VfYnkoaW4ucGVyaW9kKSAlPiUNCiAgICBncm91cF9ieSghISEgc3ltKGluZGV4X2NvbHVtbikpICU+JQ0KICAgIHN1bW1hcmlzZV9pZihpcy5udW1lcmljLCBpbi5mdW5jLCBuYS5ybSA9IG5hX3JtKQ0KfQ0KYGBgDQoNCiMjIyBBZ2dyZWdhdGUgVGVtcGVyYXR1cmUNCg0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQp0ZW1wb3JhcnkudHQgPC0gYmYudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsVEVNUCkNCmJmLmRhaWx5LnR0IDwtIHR0X3BlcmlvZF9hcHBseSh0ZW1wb3JhcnkudHQsICdkYWlseScsIGluLmZ1bmMgPSBtZWFuLCBuYV9ybSA9IFRSVUUpICU+JQ0KICBzZWxlY3QoREFURV9USU1FLCBtZWFuLnRlbXAgPSBURU1QKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1heCwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWF4LnRlbXAgPSBURU1QKSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LGJ5ID0gJ0RBVEVfVElNRScpDQpiZi5kYWlseS50dCA8LSB0dF9wZXJpb2RfYXBwbHkodGVtcG9yYXJ5LnR0LCAnZGFpbHknLCBpbi5mdW5jID0gbWluLCBuYV9ybSA9IFRSVUUpICU+JQ0KICBzZWxlY3QoREFURV9USU1FLCBtaW4udGVtcCA9IFRFTVApICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCnJtKHRlbXBvcmFyeS50dCkNCmBgYA0KDQojIyMgQ29udmVydCBXaW5kIERpcmVjdGlvbiB0byBOL1MgYW5kIEUvVyB2YWx1ZXMNCg0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQp0ZW1wb3JhcnkudHQgPC0gYmYudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsIERJUikNCiMgQ29udmVydCBkaXJlY3Rpb25zIGluIGRlZ3JlZXMgdG8gUmFkaWFucywgdGhlbiB0byB4IGFuZCB5DQp0ZW1wb3JhcnkudHQgPC0gdGVtcG9yYXJ5LnR0ICU+JQ0KICBtdXRhdGUoZGlyLnggPSByb3VuZChzaW4oRElSKnBpLzE4MCksZGlnaXRzID0gNiksDQogICAgICAgICBkaXIueSA9IHJvdW5kKGNvcyhESVIqcGkvMTgwKSxkaWdpdHMgPSA2KSkNCg0KYGBgDQoNCiMjIyBBZ2dyZWdhdGUgV2luZCBEaXJlY3Rpb24gWCBhbmQgWQ0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCnRlbXBvcmFyeS50dCA8LSB0ZW1wb3JhcnkudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsZGlyLngsIGRpci55KQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1lYW4sIG5hX3JtID0gVFJVRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsIG1lYW4uZGlyLnggPSBkaXIueCwgbWVhbi5kaXIueSA9IGRpci55KSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LCBieSA9ICdEQVRFX1RJTUUnKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1pbiwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWluLmRpci54ID0gZGlyLngsIG1pbi5kaXIueSA9IGRpci55KSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LCBieSA9ICdEQVRFX1RJTUUnKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IG1heCwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWF4LmRpci54ID0gZGlyLngsIG1heC5kaXIueSA9IGRpci55KSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LCBieSA9ICdEQVRFX1RJTUUnKQ0Kcm0odGVtcG9yYXJ5LnR0KQ0KYGBgDQoNCiMjIyBBZ2dyZWdhdGUgV2luZCBTcGVlZA0KDQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRX0NCnRlbXBvcmFyeS50dCA8LSBiZi50dCAlPiUgc2VsZWN0KERBVEVfVElNRSxTUEQpDQpiZi5kYWlseS50dCA8LSB0dF9wZXJpb2RfYXBwbHkodGVtcG9yYXJ5LnR0LCAnZGFpbHknLCBpbi5mdW5jID0gbWVhbiwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgbWVhbi5zcGQgPSBTUEQpICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCmJmLmRhaWx5LnR0IDwtIHR0X3BlcmlvZF9hcHBseSh0ZW1wb3JhcnkudHQsICdkYWlseScsIGluLmZ1bmMgPSBtYXgsIG5hX3JtID0gVFJVRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsIG1heC5zcGQgPSBTUEQpICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCmJmLmRhaWx5LnR0IDwtIHR0X3BlcmlvZF9hcHBseSh0ZW1wb3JhcnkudHQsICdkYWlseScsIGluLmZ1bmMgPSBtaW4sIG5hX3JtID0gVFJVRSkgJT4lDQogIHNlbGVjdChEQVRFX1RJTUUsIG1pbi5zcGQgPSBTUEQpICU+JQ0KICBmdWxsX2pvaW4oYmYuZGFpbHkudHQsYnkgPSAnREFURV9USU1FJykNCnJtKHRlbXBvcmFyeS50dCkNCmBgYA0KDQojIyMgQWdncmVnYXRlIFByZWNpcGl0YXRpb24NCg0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9DQp0ZW1wb3JhcnkudHQgPC0gYmYudHQgJT4lIHNlbGVjdChEQVRFX1RJTUUsIFBDUDAxKQ0KYmYuZGFpbHkudHQgPC0gdHRfcGVyaW9kX2FwcGx5KHRlbXBvcmFyeS50dCwgJ2RhaWx5JywgaW4uZnVuYyA9IHN1bSwgbmFfcm0gPSBUUlVFKSAlPiUNCiAgc2VsZWN0KERBVEVfVElNRSwgc3VtLnByZWNpcCA9IFBDUDAxKSAlPiUNCiAgZnVsbF9qb2luKGJmLmRhaWx5LnR0LGJ5ID0gJ0RBVEVfVElNRScpDQpybSh0ZW1wb3JhcnkudHQpDQpgYGANCg0KDQojIyMgQWRkIG5ldyBjb2x1bW4gZm9yIGRheXMgc2luY2UgbGFzdCBQcmVjaXBpdGF0aW9uDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQgPC0gYmYuZGFpbHkudHQgJT4lIA0KICBtdXRhdGUoZGF5cy5zaW5jZS5wcmVjaXAgPSBOQSkNCnJhaW5fZGF0YV9kZXRlY3RlZCA8LSBGQUxTRQ0KZm9yKHJvdyBpbiAxOmxlbmd0aChiZi5kYWlseS50dCRkYXlzLnNpbmNlLnByZWNpcCkpIHsNCiAgaWYocmFpbl9kYXRhX2RldGVjdGVkKSB7DQogICAgYmYuZGFpbHkudHQkZGF5cy5zaW5jZS5wcmVjaXBbcm93XSA8LSBpZmVsc2UoYmYuZGFpbHkudHQkc3VtLnByZWNpcFtyb3ddIDw9IDAuMDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShpcy5uYShiZi5kYWlseS50dCRkYXlzLnNpbmNlLnByZWNpcFtyb3ctMV0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZi5kYWlseS50dCRkYXlzLnNpbmNlLnByZWNpcFtyb3ctMV0gKyAxKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCkNCiAgfSBlbHNlIHsNCiAgICBpZihiZi5kYWlseS50dCRzdW0ucHJlY2lwW3Jvd10gPj0gMC4wMSkgew0KICAgICAgcmFpbl9kYXRhX2RldGVjdGVkIDwtIFRSVUUNCiAgICAgIGJmLmRhaWx5LnR0JGRheXMuc2luY2UucHJlY2lwW3Jvd10gPC0gMA0KICAgIH0NCiAgfQ0KICANCn0NCnJtKHJhaW5fZGF0YV9kZXRlY3RlZCxyb3cpDQpgYGANCg0KIyMgRGV0ZXJtaW5lIHdoYXQgdG8gZG8gYWJvdXQgTkEgdmFsdWVzLg0KDQpGb3IgbW9zdCBvZiB0aGUgdGltZSBzZXJpZXMgYW5hbHlzaXMgdGhlIHByb2dyYW0gbmVlZHMgdG8ga25vdyBob3cgdG8gaGFuZGxlIE5BIHZhbHVlcy4gTm90IGFuIGVhc3kgdGhpbmcgdG8gZG8gaW4gc29tZSBjYXNlcy4gT21pdHRpbmcgTkEgdmFsdWVzIGNyZWF0ZXMgYSBwcm9ibGVtIGJlY2F1c2UgdGhlIGZ1bmN0aW9ucyBhc3N1bWUgdGhhdCBhIHNlYXNvbmFsIHllYXIgaXMgMzY1IGRheXMgKGludGVnZXIgY2xvc2VzdCB0byAzNjUuMjUpLCBidXQgd2l0aCBOQSB2YWx1ZXMgbWlzc2luZyB0aGF0IHdpbGwgYmUgd3JvbmcuDQoNCmBgYHtyfQ0KdHRfbmFfc3RhdHMgPC0gZnVuY3Rpb24oaW4udGJsX3RpbWUpIHsNCiAgb3V0LnN0YXRzIDwtIGxpc3QoKQ0KICAjIG91dC5zdGF0c1tbIm5hX3BlcmNlbnQiXV0gPC0gbGlzdCgpDQogIGZvcihjb2wgaW4gbmFtZXMoaW4udGJsX3RpbWUpKSB7DQogICAgb3V0LnN0YXRzW1sibmFfcGVyY2VudCJdXVtjb2xdIDwtIDEwMCpzdW0oaXMubmEoaW4udGJsX3RpbWVbLGNvbF1bWzFdXSkpL2xlbmd0aChpbi50YmxfdGltZVssY29sXVtbMV1dKQ0KICB9DQogIHJldHVybihvdXQuc3RhdHMpDQp9DQoNCnR0X25hX3N0YXRzKGJmLmRhaWx5LnR0KQ0KYGBgDQoNClRoZXJlIGFyZSBvbmx5IGEgY291cGxlIG9mIGNvbHVtbnMgdGhhdCBoYXZlIE5BIG9yIE5hTiB2YWx1ZXMuIA0KDQoqIG1lYW4uc3BkDQoqIG1lYW4uZGlyLngNCiogbWVhbi5kaXIueQ0KKiBtZWFuLnRlbXANCg0KIyMjIE5BIHZhbHVlcyBpbiBtZWFuLnRlbXANCg0KSXQgaXMgcHJldHR5IGVhc3kgdG8gaGFuZGxlIE5BIHZhbHVlcyBpbiBURU1QLiBXZSBjYW4gZmlsbCB0aGUgcG9zaXRpb25zIHdpdGggZXhwdHJhcG9sYXRlZCB2YWx1ZXMgYmVjYXVzZSB0aGlzIHdpbGwgdXN1YWxseSBiZSBjbG9zZSBlbm91Z2guDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQkbWVhbi50ZW1wIDwtIHRydW5jKHpvbzo6bmEuYXBwcm94KGJmLmRhaWx5LnR0JG1lYW4udGVtcCkpDQp0dF9uYV9zdGF0cyhiZi5kYWlseS50dCkNCmBgYA0KDQojIyMgTkEgdmFsdWVzIGluIG1lYW4uZGlyDQoNCk5BIHZhbHVlcyBpbiBtZWFuLmRpciBsaWtlbHkgaW5kaWNhdGVzIHRoZXJlIHdhc24ndCBlbm91Z2ggZGF0YSBpbiB0aGF0IGZpZWxkIHRvIGdldCBhbiBhdmVyYWdlLiBUaGVyZWZvcmUsIHRoZSBiZXN0IGNvdXJzZSBvZiBhY3Rpb24gaXMgdG8gcmVwbGFjZSBib3RoIHZhbHVlcyB3aXRoIDAgd2hpY2ggd2lsbCBpbmRpY2F0ZSB0aGUgZGlyZWN0aW9uIHdhc24ndCBtb3ZpbmcgaW4gZWl0aGVyIHRoZSB4IGRpcmVjdGlvbiBvciB0aGUgeSBkaXJlY3Rpb24uIE5vdGUgdGhhdCB0aGlzIGNvdWxkIGNhdXNlIGlzc3VlcyB3aGVuIHRyeWluZyB0byB0dXJuIHggYW5kIHkgdmFsdWVzIGJhY2sgaW50byBjb21wYXNzIGRpcmVjdGlvbnMuDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQkbWVhbi5kaXIueCA8LSBpZmVsc2UoaXMubmEoYmYuZGFpbHkudHQkbWVhbi5kaXIueCksMCxiZi5kYWlseS50dCRtZWFuLmRpci54KQ0KYmYuZGFpbHkudHQkbWVhbi5kaXIueSA8LSBpZmVsc2UoaXMubmEoYmYuZGFpbHkudHQkbWVhbi5kaXIueSksMCxiZi5kYWlseS50dCRtZWFuLmRpci55KQ0KdHRfbmFfc3RhdHMoYmYuZGFpbHkudHQpDQpgYGANCg0KIyMjIE5BIHZhbHVlcyBpbiBtZWFuLnNwZA0KDQpOQSB2YWx1ZXMgaW4gc3BlZWQgYXJlIGEgYml0IGhhcmRlciB0byBkZWNpZGUgd2hhdCB0byBkbyB3aXRoLiBUaGVyZSBpcyBubyB3YXkgb2Yga25vd2luZyB3aGV0aGVyIHRoZSBpc3N1ZSB3YXMgdGhhdCB0aGUgbWFjaGluZSBtZWFzdXJpbmcgdGhlIGRhdGEgd2FzIGRvd24sIG9yIHdoZXRoZXIgdGhlcmUganVzdCB3YXNuJ3QgYW55IHNwZWVkLiBBbHNvLCB3aW5kIGlzIGV4dHJlbWVseSB2YXJpYWJsZSBmcm9tIGRheSB0byBkYXkuIFRoZXJlZm9yZSBpdCBpcyBkZWNpZGVkIHRvIHJlcGxhY2UgdmFsdWVzIHdpdGggMCBmb3IgdGhlIGJlZ2lubmluZyBhbmFseXNpcywgZXZlbiB0aG91Z2ggdGhpcyBtYXkgbm90IGJlIHRoZSBiZXN0IGlkZWEuDQoNCmBgYHtyfQ0KYmYuZGFpbHkudHQkbWVhbi5zcGQgPC0gaWZlbHNlKGlzLm5hKGJmLmRhaWx5LnR0JG1lYW4uc3BkKSwwLGJmLmRhaWx5LnR0JG1lYW4uc3BkKQ0KdHRfbmFfc3RhdHMoYmYuZGFpbHkudHQpDQpgYGANCg0KIyMjIEluZiB2YWx1ZXMgaW4gYW55IGNvbHVtbg0KDQpTb21lIG9mIHRoZSB2YWx1ZXMgYXJlIEluZiBiZWNhdXNlIG9mIGVycm9ycyBpbiBoYW5kbGluZyBtaXNzaW5nIGRhdGEuIFRoZXJlIGFyZSBvbmx5IGEgZmV3IGluc3RhbmNlcyBvZiB0aGlzLCB0aGVyZWZvcmUgdGhleSB3aWxsIGFsbCBiZSBjb252ZXJ0ZWQgdG8gDQoNCmBgYHtyfQ0KZm9yKGNvbCBpbiBuYW1lcyhzZWxlY3QoYmYuZGFpbHkudHQsLURBVEVfVElNRSkpKSB7DQogICAgYmYuZGFpbHkudHRbY29sXSA8LSAoMS1pcy5pbmZpbml0ZShiZi5kYWlseS50dFssY29sXVtbMV1dKSkqYmYuZGFpbHkudHRbY29sXQ0KfQ0Kcm0oY29sKQ0KYGBgDQoNCg0KIyMjIFBsb3QgVXBkYXRlZCBEYWlseSBEYXRhDQoNCiMjIyMgVGVtcGVyYXR1cmUNCg0KYGBge3J9DQp0ZW1wLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLG1heC50ZW1wLG1lYW4udGVtcCxtaW4udGVtcCksb3JkZXIuYnkgPSBmaWx0ZXJfdGltZShiZi5kYWlseS50dCwgJzIwMTInIH4gJzIwMTcnKSREQVRFX1RJTUUpDQoNCnRlbXAuZGFpbHkueHRzICU+JSBkeWdyYXBoKG1haW49IkRhaWx5IEF2ZXJhZ2UgQm93bWFuIEZpZWxkIFRlbXBlcmF0dXJlIChGKSIpICU+JQ0KICBkeUF4aXMoJ3knLCBsYWJlbCA9ICJUZW1wZXJhdHVyZSAoRikiKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNCg0KIyMjIyBXaW5kIERpcmVjdGlvbg0KDQpgYGB7cn0NCmRpci54LmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLG1lYW4uZGlyLngpLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYuZGFpbHkudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQpkaXIueC5kYWlseS54dHMgJT4lIGR5Z3JhcGgobWFpbj0iQXZlcmFnZSBEYWlseSBCb3dtYW4gRmllbGQgV2luZCBEaXJlY3Rpb24gKC0xIDwgeCA8IDEpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIkRhaWx5IFdpbmQgRGlyZWN0aW9uIChDb21wYXNzIERlZ3JlZXMpIikgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCg0KZGlyLnkuZGFpbHkueHRzIDwtIHh0cyhzZWxlY3QoZmlsdGVyX3RpbWUoYmYuZGFpbHkudHQsICcyMDEyJyB+ICcyMDE3JyksbWVhbi5kaXIueSksb3JkZXIuYnkgPSBmaWx0ZXJfdGltZShiZi5kYWlseS50dCwgJzIwMTInIH4gJzIwMTcnKSREQVRFX1RJTUUpDQoNCmRpci55LmRhaWx5Lnh0cyAlPiUgZHlncmFwaChtYWluPSJBdmVyYWdlIERhaWx5IEJvd21hbiBGaWVsZCBXaW5kIERpcmVjdGlvbiAoLTEgPCB5IDwgMSkiKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiRGFpbHkgV2luZCBEaXJlY3Rpb24gKENvbXBhc3MgRGVncmVlcykiKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNCg0KIyMjIyBXaW5kIFNwZWVkDQoNCmBgYHtyfQ0Kc3BkLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLG1lYW4uc3BkKSxvcmRlci5ieSA9IGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpJERBVEVfVElNRSkNCg0Kc3BkLmRhaWx5Lnh0cyAlPiUgZHlncmFwaChtYWluPSJBdmVyYWdlIERhaWx5IEJvd21hbiBGaWVsZCBXaW5kIFNwZWVkIChtcGgpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIldpbmQgc3BlZWQgKG1waCkiKSAlPiUNCiAgZHlSYW5nZVNlbGVjdG9yKGRhdGVXaW5kb3cgPSBjKCIyMDE2LTAxLTAxIiwiMjAxNy0xMi0zMSIpKSAlPiUNCiAgZHlVbnpvb20oKQ0KYGBgDQoNCg0KIyMjIyBQcmVjaXBpdGF0aW9uDQoNCmBgYHtyfQ0KcHJlY2lwLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLHN1bS5wcmVjaXApLG9yZGVyLmJ5ID0gZmlsdGVyX3RpbWUoYmYuZGFpbHkudHQsICcyMDEyJyB+ICcyMDE3JykkREFURV9USU1FKQ0KDQpwcmVjaXAuZGFpbHkueHRzICU+JSBkeWdyYXBoKG1haW49IkRhaWx5IEJvd21hbiBGaWVsZCBQcmVjaXBpdGF0aW9uIChJbmNoZXMpIikgJT4lDQogIGR5QXhpcygneScsIGxhYmVsID0gIlByZWNpcGl0YXRpb24gKEluKSIpICU+JQ0KICBkeVJhbmdlU2VsZWN0b3IoZGF0ZVdpbmRvdyA9IGMoIjIwMTYtMDEtMDEiLCIyMDE3LTEyLTMxIikpICU+JQ0KICBkeVVuem9vbSgpDQpgYGANCg0KIyMjIyBEYXlzIFNpbmNlIExhc3QgUmFpbg0KDQpgYGB7cn0NCmRheXMuc2luY2UucHJlY2lwLmRhaWx5Lnh0cyA8LSB4dHMoc2VsZWN0KGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpLGRheXMuc2luY2UucHJlY2lwKSxvcmRlci5ieSA9IGZpbHRlcl90aW1lKGJmLmRhaWx5LnR0LCAnMjAxMicgfiAnMjAxNycpJERBVEVfVElNRSkNCg0KZGF5cy5zaW5jZS5wcmVjaXAuZGFpbHkueHRzICU+JSBkeWdyYXBoKG1haW49IkJvd21hbiBGaWVsZCBEYXlzIFNpbmNlIFJhaW4iKSAlPiUNCiAgZHlBeGlzKCd5JywgbGFiZWwgPSAiRGF5cyBTaW5jZSBSYWluIikgJT4lDQogIGR5UmFuZ2VTZWxlY3RvcihkYXRlV2luZG93ID0gYygiMjAxNi0wMS0wMSIsIjIwMTctMTItMzEiKSkgJT4lDQogIGR5VW56b29tKCkNCmBgYA0KDQojIyBTYXZlIGRhdGEgZm9yIGZ1dHVyZSBhbmFseXNpcw0KDQpgYGB7cn0NCnNhdmUoYmYuZGFpbHkudHQsIGZpbGUgPSAiQm93bWFuRmllbGRfV2VhdGhlcl9EYWlseS5SRGF0YSIpDQpgYGANCg0K